package kubayaxi.video.client.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import kubayaxi.video.client.request.video.AddVideoRecordDTO;
import kubayaxi.video.mybatis.customize.model.video.dto.DeleteVideoDTO;
import kubayaxi.video.mybatis.customize.model.video.dto.GetIndexVideoDTO;
import kubayaxi.video.mybatis.customize.model.video.dto.GetPublishListDTO;
import kubayaxi.video.mybatis.customize.model.video.dto.GetRelatedVideoDTO;
import kubayaxi.video.client.request.video.ChangeFollowStatusDTO;
import kubayaxi.video.client.request.video.ChangeLikeStatusDTO;
import kubayaxi.video.client.request.video.GetVideoInfoDTO;
import kubayaxi.video.client.response.video.GetVideoInfoVO;
import kubayaxi.video.client.service.IVideoService;
import kubayaxi.video.mybatis.auto.dao.*;
import kubayaxi.video.mybatis.auto.model.*;
import kubayaxi.video.mybatis.customize.dao.VideoMeMapper;
import kubayaxi.video.mybatis.customize.model.video.vo.GetIndexVideoVO;
import kubayaxi.video.mybatis.customize.model.video.vo.GetPublishListVO;
import kubayaxi.video.mybatis.customize.model.video.vo.GetRelatedVideoVO;
import kubayaxi.video.service.base.common.enums.DeleteEnum;
import kubayaxi.video.service.base.common.enums.FollowStatusEnum;
import kubayaxi.video.service.base.common.enums.LikeStatusEnum;
import kubayaxi.video.service.base.common.request.BaseRequest;
import kubayaxi.video.service.base.common.treadlocal.UserContent;
import kubayaxi.video.service.base.common.vo.CommonPageResult;
import kubayaxi.video.service.base.util.common.Asserts;
import kubayaxi.video.service.base.util.transaction.TransactionUtil;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class VideoServiceImpl implements IVideoService {

    @Resource
    private VideoMapper videoMapper;
    @Resource
    private VideoMeMapper videoMeMapper;
    @Resource
    private ClientUserMapper clientUserMapper;
    @Resource
    private ChannelMapper channelMapper;
    @Resource
    private LikeRecordMapper likeRecordMapper;
    @Resource
    private FollowRelativeMapper followRelativeMapper;
    @Resource
    private HistoryRecordMapper historyRecordMapper;
    @Resource
    private TransactionUtil transactionUtil;
    @Resource
    private RecommendRecordMapper recommendRecordMapper;

    /**
     * 获取首页推荐列表数据
     */
    @Override
    public List<GetIndexVideoVO> getIndexVideo(BaseRequest<GetIndexVideoDTO> dto) {
//        分页查询的代码,舍弃
//        Page page = new Page(dto.getPageNo(),dto.getPageSize());
//        IPage<GetIndexVideoVO> indexVideo = videoMeMapper.getIndexVideo(page);
//        CommonPageResult<GetIndexVideoVO> ans = new CommonPageResult<>();
//        ans.setTotal(indexVideo.getTotal());
//        ans.setList(indexVideo.getRecords());

        //协同过滤算法
        //根据用户的收藏喜好行为计算相似度，给有相近的用户行为推荐视频
        //比如：
        //用户1收藏了视频1、2、3
        //用户2收藏了视频1、3、6
        //用户3 收藏了视频1
        //那根据协同过滤算法推荐，用户3可能也喜欢视频3
        GetIndexVideoDTO data = dto.getData();
        Asserts.isNotNull(data.getNum(),"请求数量为空");
        Long userId = UserContent.get();
        //查询当前用户已经看过的视频
        List<HistoryRecord> nowUserLookedList = historyRecordMapper.selectList(new LambdaQueryWrapper<HistoryRecord>()
                .eq(HistoryRecord::getUId, userId)
                .eq(HistoryRecord::getDeleteStatus, DeleteEnum.NOT_DELETED.getCode()));
        List<Long> nowUserLookedIdList = nowUserLookedList.stream().map(HistoryRecord::getVId).collect(Collectors.toList());
        //查询当前用户已经推荐过的视频
        List<Long> nowUserRecommendIdList = recommendRecordMapper.selectList(new LambdaQueryWrapper<RecommendRecord>()
                        .eq(RecommendRecord::getUId, userId)
                        .eq(RecommendRecord::getDeleteStatus, DeleteEnum.NOT_DELETED.getCode()))
                .stream().map(RecommendRecord::getVId).collect(Collectors.toList());
        //存放视频出现数量,数量越多代表推测的越准. key:视频id value:该视频被看过的用户数
        Map<Long,Integer> map = new HashMap<>();
        for (HistoryRecord nowUserLookedItem : nowUserLookedList) {
            //查询看过这个视频的用户
            List<HistoryRecord> lookedUserList = historyRecordMapper.selectList(new LambdaQueryWrapper<HistoryRecord>()
                    .eq(HistoryRecord::getVId, nowUserLookedItem.getVId())
                    .eq(HistoryRecord::getDeleteStatus, DeleteEnum.NOT_DELETED.getCode()));
            List<Long> lookedUserIdList = lookedUserList.stream().map(HistoryRecord::getUId).collect(Collectors.toList());
            //查询这些用户都看过哪些视频,并且当前用户没有看过,并且没有给当前用户推荐过
            List<HistoryRecord> nowUserNotLookList = historyRecordMapper.selectList(new LambdaQueryWrapper<HistoryRecord>()
                    .in(CollUtil.isNotEmpty(lookedUserIdList),HistoryRecord::getUId, lookedUserIdList)
//                    .notIn(CollUtil.isNotEmpty(nowUserLookedIdList),HistoryRecord::getVId, nowUserLookedIdList)
//                    .notIn(CollUtil.isNotEmpty(nowUserRecommendIdList),HistoryRecord::getVId,nowUserRecommendIdList)
                    .eq(HistoryRecord::getDeleteStatus, DeleteEnum.NOT_DELETED.getCode()));
            for (HistoryRecord nowUserNotLookItem : nowUserNotLookList) {
                map.put(nowUserNotLookItem.getVId(),map.getOrDefault(nowUserNotLookItem.getVId(),0) + 1);
            }
        }
        //根据map的value排序将key存放到list
        List<Map.Entry<Long, Integer>> list = new ArrayList<>(map.entrySet());
        list.sort((k1, k2) -> k2.getValue().compareTo(k1.getValue()));
        //查出来的推荐视频数大于需求的数,截取num个去查详情
        List<GetIndexVideoVO> ans;
        if(list.size() >= data.getNum()){
            List<Map.Entry<Long, Integer>> subList = list.subList(0, data.getNum());
            List<Long> videoIdList = subList.stream().map(Map.Entry::getKey).collect(Collectors.toList());
            ans = videoMeMapper.getIndexVideo(videoIdList);
        } else{ //查num个没看过的并且没推荐过的
            ans = videoMeMapper.getIndexVideo2(data.getNum(),userId);
        }
        //推荐记录表添加记录
        for (GetIndexVideoVO an : ans) {
            RecommendRecord recommendRecord = new RecommendRecord();
            recommendRecord.setUId(userId);
            recommendRecord.setVId(an.getVId());
            recommendRecordMapper.insert(recommendRecord);
        }
        return ans;
    }

    /**
     * 根据视频id获取视频详情
     */
    @Override
    public GetVideoInfoVO getVideoInfo(BaseRequest<GetVideoInfoDTO> dto) {
        GetVideoInfoDTO data = dto.getData();
        Asserts.isNotNull(data.getVId(),"视频id为空");
        Video video = videoMapper.selectById(data.getVId());
        Asserts.isNotNull(video,"视频不存在");
        ClientUser clientUser = clientUserMapper.selectById(video.getUId());
        Asserts.isNotNull(clientUser,"视频发布用户不存在");
        Channel channel = channelMapper.selectById(video.getCId());
        Asserts.isNotNull(channel,"视频所属频道不存在");
        FollowRelative followRelative = followRelativeMapper.selectOne(new LambdaQueryWrapper<FollowRelative>()
                .eq(FollowRelative::getDeleteStatus, DeleteEnum.NOT_DELETED.getCode())
                .eq(FollowRelative::getUId, UserContent.get())
                .eq(FollowRelative::getBeuId, video.getUId()));
        LikeRecord likeRecord = likeRecordMapper.selectOne(new LambdaQueryWrapper<LikeRecord>()
                .eq(LikeRecord::getDeleteStatus, DeleteEnum.NOT_DELETED.getCode())
                .eq(LikeRecord::getUId, UserContent.get()).eq(LikeRecord::getVId, video.getId()));
        return GetVideoInfoVO.builder()
                .title(video.getTitle()) //视频数据
                .url(video.getUrl())
                .coverUrl(video.getCoverUrl())
                .playNum(video.getPlayNum())
                .desc(video.getDesc())
                .likeNum(video.getLikeNum())
                .shareNum(video.getShareNum())
                .commentNum(video.getCommentNum())
                .authId(clientUser.getId()) //作者数据
                .portraitUrl(clientUser.getPortraitUrl())
                .nickName(clientUser.getNickName())
                .authCreateTime(clientUser.getCreatedTime())
                .channel(channel.getTitle()) //频道数据
                .cId(channel.getId())
                //状态数据
                .followStatus(followRelative == null ? FollowStatusEnum.NO_FOLLOW.getCode() : FollowStatusEnum.FOLLOWED.getCode())
                .likeStatus(likeRecord == null ? LikeStatusEnum.NO_LIKE.getCode() : LikeStatusEnum.LIKE.getCode())
                .build();
    }

    /**
     * 修改关注状态
     */
    @Override
    public void changeFollowStatus(BaseRequest<ChangeFollowStatusDTO> dto) {
        ChangeFollowStatusDTO data = dto.getData();
        Asserts.isNotNull(data.getFollowStatus(),"关注状态为空");
        Asserts.isNotNull(data.getBeuId(),"被关注者id为空");
        Long uId = UserContent.get();
        FollowStatusEnum byCode = FollowStatusEnum.getByCode(data.getFollowStatus());
        Asserts.isNotNull(byCode,"关注状态错误");
        FollowRelative followRelative = followRelativeMapper.selectOne(new LambdaQueryWrapper<FollowRelative>()
                .eq(FollowRelative::getBeuId, data.getBeuId()).eq(FollowRelative::getUId, uId));
        // 当前已经关注并且传递的是开启关注,报错
        if (ObjectUtil.isNotNull(followRelative) && data.getFollowStatus().equals(FollowStatusEnum.FOLLOWED.getCode())) {
            Asserts.isTrue(false,"当前用户已关注");
        }
        // 没有关注并且是关闭关注,报错
        if (ObjectUtil.isNull(followRelative) && data.getFollowStatus().equals(FollowStatusEnum.NO_FOLLOW.getCode())) {
            Asserts.isTrue(false,"当前用户还并未关注");
        }
        // 没有关注过,关注
        if(ObjectUtil.isNull(followRelative) && data.getFollowStatus().equals(FollowStatusEnum.FOLLOWED.getCode())) {
            // 事务更新插入关系数据,更新当前用户关注者数量,被关注者粉丝数量
            boolean b = transactionUtil.transactional(() -> {
                FollowRelative newFollowRelative = new FollowRelative();
                newFollowRelative.setBeuId(data.getBeuId());
                newFollowRelative.setUId(uId);
                int insert = followRelativeMapper.insert(newFollowRelative);
                int update1 = clientUserMapper.update(null, new LambdaUpdateWrapper<ClientUser>()
                        .eq(ClientUser::getId, uId).setSql("follow_num = follow_num + 1"));
                int update2 = clientUserMapper.update(null, new LambdaUpdateWrapper<ClientUser>()
                        .eq(ClientUser::getId, data.getBeuId()).setSql("fan_num = fan_num + 1"));
                return insert == 1 && update1 == 1 && update2 == 1;
            });
            Asserts.isTrue(b,"添加关注失败");
            // 关注了,取消关注
        } else if (ObjectUtil.isNotNull(followRelative) && data.getFollowStatus().equals(FollowStatusEnum.NO_FOLLOW.getCode())) {
            boolean b = transactionUtil.transactional(() -> {
                int delete = followRelativeMapper.deleteById(followRelative.getId());
                int update1 = clientUserMapper.update(null, new LambdaUpdateWrapper<ClientUser>()
                        .eq(ClientUser::getId, uId).setSql("follow_num = follow_num - 1"));
                int update2 = clientUserMapper.update(null, new LambdaUpdateWrapper<ClientUser>()
                        .eq(ClientUser::getId, data.getBeuId()).setSql("fan_num = fan_num - 1"));
                return delete == 1 && update1 == 1 && update2 == 1;
            });
            Asserts.isTrue(b,"删除关注信息失败");
        }
    }

    /**
     * 修改喜欢状态
     */
    @Override
    public void changeLikeStatus(BaseRequest<ChangeLikeStatusDTO> dto) {
        ChangeLikeStatusDTO data = dto.getData();
        Asserts.isNotNull(data.getVId(),"视频id为空");
        Asserts.isNotNull(data.getLikeStatus(),"喜欢状态为空");
        LikeStatusEnum statusEnum = LikeStatusEnum.getByCode(data.getLikeStatus());
        Asserts.isNotNull(statusEnum,"喜欢状态错误");
        Long userId = UserContent.get();
        LikeRecord likeRecord = likeRecordMapper.selectOne(new LambdaQueryWrapper<LikeRecord>()
                .eq(LikeRecord::getVId, data.getVId())
                .eq(LikeRecord::getUId, userId)
                .eq(LikeRecord::getDeleteStatus, DeleteEnum.NOT_DELETED.getCode()));
        // 没有喜欢记录
        if (ObjectUtil.isNull(likeRecord)) {
            // 设置为喜欢
            if(data.getLikeStatus().equals(LikeStatusEnum.LIKE.getCode())) {
                LikeRecord record = new LikeRecord();
                record.setUId(userId);
                record.setVId(data.getVId());
                int insert = likeRecordMapper.insert(record);
                Asserts.isTrue(insert == 1, "添加喜欢记录失败");
                videoMapper.update(null,new LambdaUpdateWrapper<Video>().eq(Video::getId,data.getVId()).setSql("like_num = like_num + 1"));
            } else { // 设置为不喜欢
                Asserts.isTrue(false,"当前用户还并未喜欢该视频");
            }
        } else { // 有喜欢记录
            // 设置为不喜欢
            if(data.getLikeStatus().equals(LikeStatusEnum.NO_LIKE.getCode())) {
                int delete = likeRecordMapper.deleteById(likeRecord);
                Asserts.isTrue(delete == 1, "删除喜欢记录失败");
                videoMapper.update(null,new LambdaUpdateWrapper<Video>().eq(Video::getId,data.getVId()).setSql("like_num = like_num - 1"));
            } else {
                Asserts.isTrue(false,"当前用户已喜欢该视频");
            }
        }
    }

    /**
     * 获取相关视频推荐列表
     */
    @Override
    public List<GetRelatedVideoVO> getRelatedVideo(BaseRequest<GetRelatedVideoDTO> dto) {
        GetRelatedVideoDTO data = dto.getData();
        Asserts.isNotNull(data.getCId(),"频道id为空");
        Asserts.isNotNull(data.getVId(),"视频id为空");
        List<GetRelatedVideoVO> ans = videoMeMapper.getRelatedVideo(data);
        return ans;
    }

    /**
     * 添加用户观看视频记录以及增加视频播放量
     */
    @Override
    public void addVideoRecord(BaseRequest<AddVideoRecordDTO> dto) {
        AddVideoRecordDTO data = dto.getData();
        Asserts.isNotNull(data.getVId(),"视频id为空");
        Long userId = UserContent.get();
        HistoryRecord historyRecord = historyRecordMapper.selectOne(new LambdaQueryWrapper<HistoryRecord>()
                .eq(HistoryRecord::getUId, userId)
                .eq(HistoryRecord::getVId, data.getVId())
                .eq(HistoryRecord::getDeleteStatus, DeleteEnum.NOT_DELETED.getCode()));
        //已经有观看记录,就把观看记录提前
        if(ObjectUtil.isNotNull(historyRecord)){
            historyRecord.setEditTime(LocalDateTime.now());
            int update = historyRecordMapper.updateById(historyRecord);
            Asserts.isTrue(update == 1,"修改观看记录失败");
        } else{ //没有观看记录就添加
            HistoryRecord record = new HistoryRecord();
            record.setUId(userId);
            record.setVId(data.getVId());
            int insert = historyRecordMapper.insert(record);
            Asserts.isTrue(insert == 1, "添加观看记录失败");
        }
        //增加视频播放量
        addVideoPlayNum(data.getVId());
    }

    /**
     * 删除视频
     */
    @Override
    public void deleteVideo(BaseRequest<DeleteVideoDTO> dto) {
        DeleteVideoDTO data = dto.getData();
        Asserts.isNotNull(data.getVId(),"视频id为空");
        Video video = videoMapper.selectById(data.getVId());
        Asserts.isNotNull(video,"视频为空");
        video.setDeleteStatus(DeleteEnum.DELETED.getCode());
        int delete = videoMapper.updateById(video);
        Asserts.isTrue(delete == 1, "删除失败");
    }

    /**
     * 获取发布列表
     */
    @Override
    public CommonPageResult<GetPublishListVO> getPublishList(BaseRequest<GetPublishListDTO> dto) {
        GetPublishListDTO data = dto.getData();
        Asserts.isNotNull(data.getUId(),"用户id为空");
        Page page = new Page(dto.getPageNo(),dto.getPageSize());
        IPage<GetPublishListVO> list = videoMeMapper.getPublishList(page, data);
        CommonPageResult<GetPublishListVO> ans = new CommonPageResult<>();
        ans.setList(list.getRecords());
        ans.setTotal(list.getTotal());
        return ans;
    }

    /**
     * 增加视频播放量
     */
    private void addVideoPlayNum(Long vId) {
        int b = videoMapper.update(null, new LambdaUpdateWrapper<Video>().eq(Video::getId, vId).setSql("play_num = play_num + 1"));
        Asserts.isTrue(b == 1, "增加视频播放量失败");
    }

}
